
#include "QFPort.hpp"
#include "QAssert.hpp"
#include "QFPkg.hpp"

namespace QP {
Q_DEFINE_THIS_MODULE("QFPort")

// Global-scope objects -----------------------------------------------------
pthread_mutex_t QF_pThreadMutex_ = PTHREAD_MUTEX_INITIALIZER;

// Local objects *************************************************************
static pthread_mutex_t l_startupMutex;
static bool l_isRunning;  // flag indicating when QF is running
static struct timespec l_tick;
enum { NANOSLEEP_NSEC_PER_SEC = 1000000000 };

// QF functions ==============================================================
void QF::init(void) {
  // init the global mutex with the default non-recursive initializer
  pthread_mutex_init(&QF_pThreadMutex_, NULL);

  // init the startup mutex with the default non-recursive initializer
  pthread_mutex_init(&l_startupMutex, NULL);

  // lock the startup mutex to block any active objects started before
  // calling QF::run()
  pthread_mutex_lock(&l_startupMutex);
  l_tick.tv_sec = 0;
  l_tick.tv_nsec = NANOSLEEP_NSEC_PER_SEC / 100L;  // default clock tick
}

int QF::run(void) {
  onStartup();  // application-specific startup callback

  pthread_mutex_unlock(&l_startupMutex);
  l_isRunning = true;
  while (l_isRunning) {
    QF_onClockTick();
    nanosleep(&l_tick, NULL);
  }
  onCleanup();  // cleanup callback
  pthread_mutex_destroy(&l_startupMutex);
  pthread_mutex_destroy(&QF_pThreadMutex_);
  return 0;
}

//............................................................................
void QF_setTickRate(uint32_t ticksPerSec) {
  l_tick.tv_nsec = NANOSLEEP_NSEC_PER_SEC / ticksPerSec;
}

//............................................................................
void QF::stop(void) {
  l_isRunning = false;  // stop the loop in QF::run()
}

//****************************************************************************
void QF_enterCriticalSection_(void) { pthread_mutex_lock(&QF_pThreadMutex_); }
//****************************************************************************
void QF_leaveCriticalSection_(void) { pthread_mutex_unlock(&QF_pThreadMutex_); }

void QF::thread_(QActive *act) {
  if (act->canCallJava()) {
    act->javaAttachThread();
  }
  // loop until m_thread is cleared in QActive::stop()
  do {
    auto e = act->get_();  // wait for event
    act->dispatch(e);      // dispatch to the active object's state machine
  } while (act->m_thread != static_cast<uint8_t>(0));

  QF::remove_(act);  // remove this object from any subscriptions
  pthread_cond_destroy(&act->m_osObject);  // cleanup the condition variable
  if (act->canCallJava()) {
    act->javaDetachThread();
  }
}

static void *ao_thread(void *arg) {  // the expected POSIX signature
  QF::thread_(static_cast<QActive *>(arg));
  return static_cast<void *>(0);  // return success
}

void QActive::start(uint8_t const prio, std::shared_ptr<const QEvt> qSto[],
                    uint32_t const qLen, std::shared_ptr<const QEvt> ie) {
  m_eQueue.init(qSto, qLen);
  m_prio = prio;
  QF::add_(this);
  this->init(ie);

  pthread_attr_t attr;
  pthread_attr_init(&attr);

  // SCHED_FIFO corresponds to real-time preemptive priority-based scheduler
  // NOTE: This scheduling policy requires the superuser privileges
  pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
  // see NOTE04
  struct sched_param param;
  param.sched_priority = prio + (sched_get_priority_max(SCHED_FIFO) - QF_MAX_ACTIVE - 3);

  pthread_attr_setschedparam(&attr, &param);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

  pthread_t thread;
  if (pthread_create(&thread, &attr, &ao_thread, this) != 0) {
    // Creating the p-thread with the SCHED_FIFO policy failed.
    // Most probably this application has no superuser privileges,
    // so we just fall back to the default SCHED_OTHER policy
    // and priority 0.
    pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
    param.sched_priority = 0;
    pthread_attr_setschedparam(&attr, &param);
    Q_ALLEGE(pthread_create(&thread, &attr, &ao_thread, this) == 0);
  }
  pthread_attr_destroy(&attr);
  m_thread = static_cast<uint8_t>(1);
}

void QActive::stop(void) {
  m_thread = static_cast<uint8_t>(0);  // stop the QF::thread_() loop
}
}
